/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.oron3.bouncer.genericgl;

import javax.microedition.khronos.opengles.GL10;

import android.opengl.GLU;
import android.opengl.Matrix;

/**
 * A utility that projects
 *
 */
public class Projector {
	private final int[] mView;
	public Projector() {
		mMVP = new float[16];
		mV = new float[4];
		mView = new int[4];
		mGrabber = new MatrixGrabber();
	}

	public void setCurrentView(final int x, final int y, final int width, final int height) {

		mView[0] = mX = x;
		mView[1] = mY = y;
		mView[2] = mViewWidth = width;
		mView[3] = mViewHeight = height;
	}

	public void project(final float[] obj, final int objOffset, final float[] win, final int winOffset) {
		if (!mMVPComputed) {
			Matrix.multiplyMM(mMVP, 0, mGrabber.mProjection, 0, mGrabber.mModelView, 0);
			mMVPComputed = true;
		}

		Matrix.multiplyMV(mV, 0, mMVP, 0, obj, objOffset);

		final float rw = 1.0f / mV[3];

		win[winOffset] = mX + mViewWidth * (mV[0] * rw + 1.0f) * 0.5f;
		win[winOffset + 1] = mY + mViewHeight * (mV[1] * rw + 1.0f) * 0.5f;
		win[winOffset + 2] = (mV[2] * rw + 1.0f) * 0.5f;
	}
	/**
	 * Calculates the transform from screen coordinate
	 * system to world coordinate system coordinates
	 * for a specific point, given a camera position.
	 *
	 * @param touch Vec2 point of screen touch, the
	      actual position on physical screen (ej: 160, 240)
	 * @param cam camera object with x,y,z of the
	      camera and screenWidth and screenHeight of
	      the device.
	 * @return position in WCS.
	 */
	//	public void unproject(final float[] win, final int winOffset,final float[] obj, final int objOffset)
	//	{  
	//
	//		final float screenW = mViewWidth;
	//		final float screenH = mViewHeight;
	//
	//		// Auxiliary matrix and vectors
	//		// to deal with ogl.
	//		float[] invertedMatrix, transformMatrix,
	//		normalizedInPoint, outPoint;
	//		invertedMatrix = new float[16];
	//		transformMatrix = new float[16];
	//		normalizedInPoint = new float[4];
	//		outPoint = new float[4];
	//
	//		// Invert y coordinate, as android uses
	//		// top-left, and ogl bottom-left.
	//		final int oglTouchY = (int) (screenH - win[winOffset+1]);
	//
	//		/* Transform the screen point to clip
	//		       space in ogl (-1,1) */       
	//		normalizedInPoint[0] =
	//				(float) ((win[winOffset]) * 2.0f / screenW - 1.0);
	//		normalizedInPoint[1] =
	//				(float) ((oglTouchY) * 2.0f / screenH - 1.0);
	//		normalizedInPoint[2] = win[2];
	//		normalizedInPoint[3] = 1.0f;
	//
	//		/* Obtain the transform matrix and
	//		       then the inverse. */
	//		Matrix.multiplyMM(
	//				transformMatrix, 0,
	//				mGrabber.mProjection, 0,
	//				mGrabber.mModelView, 0);
	//		Matrix.invertM(invertedMatrix, 0,
	//				transformMatrix, 0);       
	//
	//		/* Apply the inverse to the point
	//		       in clip space */
	//		Matrix.multiplyMV(
	//				outPoint, 0,
	//				invertedMatrix, 0,
	//				normalizedInPoint, 0);
	//
	//		if (outPoint[3] == 0.0)
	//		{
	//			// Avoid /0 error.
	//			Log.e("World coords", "ERROR!");
	//		}
	//		obj[objOffset] = outPoint[0] / outPoint[3];
	//		obj[objOffset+1] = outPoint[1] / outPoint[3];
	//		obj[objOffset+2] = outPoint[2] / outPoint[3];
	//
	//	}

	public void unproject(final float[] win, final int winOffset,final float[] obj, final int objOffset)
	{  
		GLU.gluUnProject(win[winOffset], mViewHeight - win[winOffset+1], win[2], mGrabber.mModelView, 0, mGrabber.mProjection, 0, mView, 0, obj, objOffset);
		obj[0] /= obj[3];
		obj[1] /= obj[3];
		obj[2] /= obj[3];
		obj[3] = 1;


	}
	/**
	 * Get the current projection matrix. Has the side-effect of
	 * setting current matrix mode to GL_PROJECTION
	 * @param gl
	 */
	public void initCurrentProjection(final GL10 gl) {
		mGrabber.getCurrentProjection(gl);
		mMVPComputed = false;
	}

	/**
	 * Get the current model view matrix. Has the side-effect of
	 * setting current matrix mode to GL_MODELVIEW
	 * @param gl
	 */
	public void initCurrentModelView(final GL10 gl) {
		mGrabber.getCurrentModelView(gl);
		mMVPComputed = false;
	}

	public float[] getCurrentModelView() {
		return mGrabber.mModelView;
	}
	public float[] getCurrentProjection() {
		return mGrabber.mProjection;
	}
	private final MatrixGrabber mGrabber;
	private boolean mMVPComputed;
	private final float[] mMVP;
	private final float[] mV;
	private int mX;
	private int mY;
	private int mViewWidth;
	private int mViewHeight;
}
